# -*- coding: utf-8 -*-
import os
import wx
import Globals
import History

appName = "메모장"

class NotepadFrame(wx.Frame):
    def __init__(self, parent, title):
        wx.Frame.__init__(self, parent, title=title, size=(600, 400))
        self.control = wx.TextCtrl(self, style=wx.TE_MULTILINE | wx.TE_NOHIDESEL)
        self.CreateStatusBar() # A StatusBar in the bottom of the window
        self.SetIcon(wx.Icon('notepad.ico', wx.BITMAP_TYPE_ICO, 16, 16))
        
        # Setting up the menu.
        menuFile = wx.Menu()
        menuEdit = wx.Menu()
        menuStyle = wx.Menu()
        menuView = wx.Menu()
        menuHelp = wx.Menu()

        # wx.ID_ABOUT and wx.ID_EXIT are standard ids provided by wxWidgets.
        menuItemNew = menuFile.Append(wx.ID_NEW, "New\tCtrl+N", "")
        menuItemOpen = menuFile.Append(wx.ID_OPEN, "&Open...\tCtrl+O", " Open a file")
        menuItemSave = menuFile.Append(wx.ID_SAVE, "&Save\tCtrl+S", "")
        menuItemSaveAs = menuFile.Append(wx.ID_SAVEAS, "Save &as...", "")
        menuItemExit = menuFile.Append(wx.ID_EXIT, "E&xit", " Terminate the program")

        menuItemAbout = menuFile.Append(wx.ID_ABOUT, "&About", " Information about this program")

        menuItemUndo = menuEdit.Append(-1, "&Undo\tCtrl+Z", "")
        menuItemRedo = menuEdit.Append(-1, "Redo\tCtrl+Y", "")
        menuItemFind = menuEdit.Append(-1, "Find...\tCtrl+F", "")
        menuItemFindNext = menuEdit.Append(-1, "Find next\tF3", "")
        menuItemReplace = menuEdit.Append(-1, "Replace...\tCtrl+H", "")
        menuItemGoto = menuEdit.Append(-1, "Goto...\tCtrl+G", "")
        menuItemSelectAll = menuEdit.Append(-1, "Select all\tCtrl+A", "")

        # Creating the menubar.
        menuBar = wx.MenuBar()
        menuBar.Append(menuFile, "&File") # Adding the "filemenu" to the MenuBar
        menuBar.Append(menuEdit, "&Edit") # Adding the "filemenu" to the MenuBar
        self.SetMenuBar(menuBar)  # Adding the MenuBar to the Frame content.

        # Set events.
        self.Bind(wx.EVT_MENU, self.OnNew, menuItemNew)
        self.Bind(wx.EVT_MENU, self.OnOpen, menuItemOpen)
        self.Bind(wx.EVT_MENU, self.OnSave, menuItemSave)
        self.Bind(wx.EVT_MENU, self.OnSaveAs, menuItemSaveAs)
        self.Bind(wx.EVT_MENU, self.OnExit, menuItemExit)
        self.Bind(wx.EVT_TEXT, self.OnText, self.control)
        
        self.Bind(wx.EVT_MENU, self.OnUndo, menuItemUndo)
        self.Bind(wx.EVT_MENU, self.OnRedo, menuItemRedo)
        self.Bind(wx.EVT_MENU, self.OnShowFind, menuItemFind)
        self.Bind(wx.EVT_MENU, self.OnFind, menuItemFindNext)
        self.Bind(wx.EVT_MENU, self.OnShowReplace, menuItemReplace)
        self.Bind(wx.EVT_MENU, self.OnGoto, menuItemGoto)
        self.Bind(wx.EVT_MENU, self.OnSelectAll, menuItemSelectAll)

        self.Bind(wx.EVT_FIND, self.OnFind)
        self.Bind(wx.EVT_FIND_NEXT, self.OnFind)
        self.Bind(wx.EVT_FIND_REPLACE, self.OnReplace)
        self.Bind(wx.EVT_FIND_REPLACE_ALL, self.OnReplaceAll)
        self.Bind(wx.EVT_FIND_CLOSE, self.OnFindClose)
        
        # control 에 bind 해야함. carret 변화
        self.control.Bind(wx.EVT_CHAR, self.OnChar)
        
        # 이 이벤트는 마우스가 움직일 때 마다 발생( 클릭하면 발생하는 게 아님!! )
        # 클릭 이벤트는 못찾겠음 -.-, control 이 click 발생을 받는 것이 원하는 것        
        #self.Bind(wx.EVT_SET_CURSOR, self.OnChangePosition)        
        self.control.Bind(wx.EVT_SET_CURSOR, self.OnChangePosition)
        
#        #게속 불림 이유 모름
#        self.Bind(wx.EVT_CLOSE, self.OnClose)

        self.InitValues()
        self.Show(True)

        Globals.mainFrame = self
        Globals.textControl = self.control
       
        
    def OnClose(self,e):
        print 'close'
        self.MyClose(e)
        
###########################################
# inner(private)
###########################################

    ########################################
    # check to close with button 'x' or alf f4 etc )    
    
    # doesn't work, what is dtor that I want to use
    def __del__(self,e):
        print 'dtor'
        self.CheckModified(e)
    
    def MyClose(self,e):
        self.CheckModified(e)
        # Close the frame. True : force
        self.Close(True)
    
    ######################################
    # value & modified
    def InitValues(self):
        self.dirname = ""
        self.filename = "제목 없음"
        self.control.SetValue("")   # TextCtrl.SetValue 를 호출하면  wx.EVT_TEXT 가 발생함        
        self.SetTitle(self.filename + " - " + appName)
        self.findReplaceData = wx.FindReplaceData()
        self.historyMgr = History.HistoryManager()
        self.valueOrg = ""
        
    def SetValues(self):
        self.valueOrg = self.control.GetValue()
        
    def IsModified(self):
        if self.valueOrg == self.control.GetValue():
            return False
        return True
    
    #######################################
    # file
    def GetFilePath(self, dirname, filename):
        # wxpython tutorial 의 맨 마지막 쯤, OS independent path concatenation 항목 참조
        return os.path.normpath(os.path.join(self.dirname + os.sep, self.filename))
    
    def CheckModified(self, e):
        if self.IsModified():
            dlg = wx.MessageDialog(self
                                   , self.GetFilePath(self.dirname, self.filename)
                                    + " 파일의 내용이 변경되었습니다.\n\n변경된 내용을 저장하시겠습니까?"
                                   , "메모장", wx.YES_NO | wx.CANCEL | wx.ICON_EXCLAMATION)
            ret = dlg.ShowModal()
            if ret == wx.ID_CANCEL:
                return wx.ID_CANCEL
            
            if ret == wx.ID_YES:
                self.OnSave(e)
    
    def CheckExistingFile(self, dirname, filename):            
        if os.path.exists(self.GetFilePath(dirname, filename)):
            dlg = wx.MessageDialog(self
                                   , filename
                                   + "이(가) 이미 있습니다.\n\n바꾸시겠습니까?"
                                   , "다른 이름으로 저장 확인", wx.YES_NO | wx.NO_DEFAULT | wx.ICON_EXCLAMATION)
            if dlg.ShowModal() == wx.ID_NO:
                return False
        return True
    
    def Save(self):
        if not self.IsModified():
            return
        
        filepath = self.GetFilePath(self.dirname, self.filename)
        self.control.SaveFile(filepath)
        self.SetValues()
        self.SetTitle(self.filename + " - " + appName)
        
    ###################################
    # carret
    def GetCurrentCarret(self):
        pos = self.control.GetInsertionPoint()
        (x,y)= self.control.PositionToXY(pos)
        self.SetStatusText("line:%d col:%d" %(y+1,x))
        print("pos:%d line:%d col:%d" %(pos,y+1,x))
        
        #bug : test file에서 line이 3029 다음 1로 reset
        #pos가 long 

        
###########################################
# file menu
########################################### 
    def OnNew(self, e):
        """ New file """
        if self.CheckModified(e) == wx.ID_CANCEL:
            return
        self.InitValues()

    
                
    def OnOpen(self, e):
        """ Open a file"""
        if self.CheckModified(e) == wx.ID_CANCEL:
            return
        
        dlg = wx.FileDialog(self, "열기", self.dirname, "*.txt"
                            , "텍스트 문서(*.txt)|*.txt|모든 파일|*.*", wx.OPEN)
        if dlg.ShowModal() == wx.ID_OK:
            self.InitValues() #file open시에도 초기화 한 후 셋팅한다.
            self.filename = dlg.GetFilename()
            self.dirname = dlg.GetDirectory()
            filepath = self.GetFilePath(self.dirname, self.filename)
            self.control.LoadFile(filepath)
            self.SetValues()
            self.SetTitle(self.filename + " - " + appName)
        dlg.Destroy()
    
        
    def OnSave(self, e):
        """ Save a file"""
        if self.dirname and self.filename: 
            self.Save()            
        else:
            # 메모장에서 새파일인 경우 save 를 하면 save as 로 넘어감. 메모장과 똑같이 맞춤
            self.OnSaveAs(e)

    def OnSaveAs(self, e):
        """ Save a file as..."""
        if self.control.IsEmpty():
            return
        
        dlg = wx.FileDialog(self, "다른 이름으로 저장", self.dirname, "*.txt"
                            , "텍스트 문서(*.txt)|*.txt|모든 파일|*.*", wx.SAVE)
        if dlg.ShowModal() == wx.ID_OK:
            if self.CheckExistingFile(dlg.GetDirectory(), dlg.GetFilename()):
                self.filename = dlg.GetFilename()
                self.dirname = dlg.GetDirectory()
                self.Save()

        dlg.Destroy()
 
    def OnAbout(self, e):
        # A message dialog box with an OK button. wx.OK is a standard ID in wxWidgets.
        dlg = wx.MessageDialog(self, "A small text editor", "About Sample Editor", wx.OK)
        # Show it
        dlg.ShowModal()
        # finally destroy it when finished.
        dlg.Destroy()

    def OnExit(self, e):            
        self.MyClose(e)
            
            
    ###########################
    # change
    
    def OnChangePosition(self, e):
        self.GetCurrentCarret()
        e.Skip()

    def OnChar(self, e):
                
        #todo : bug ( col이 이상함. ->* <- 일때 증가 등)
        #입력후 enter를 치면 pos는 변하나, 라인은 그대로/컬럼은 증가
        #내부적인 문제?
        self.GetCurrentCarret()
        
        keycode = e.GetKeyCode()
        if keycode == wx.WXK_BACK and self.control.GetInsertionPoint() > 0:
            #todo: wx index 로 변환해야 함
            pos = self.control.GetInsertionPoint() - 1
            uchar = self.control.GetValue()[pos]
            self.historyMgr.AddDeleteCmd(pos, uchar)
        elif keycode == wx.WXK_DELETE and len(self.control.GetValue()) > 0:
            #todo: wx index 로 변환해야 함
            pos = self.control.GetInsertionPoint()
            uchar = self.control.GetValue()[pos]
            self.historyMgr.AddDeleteCmd(pos, uchar)
        else:
            #todo: uchar는 텍스트여야한다.
            uchar = unichr(e.GetKeyCode())
            pos = self.control.GetInsertionPoint()
            self.historyMgr.AddInsertCmd(pos, uchar)
        e.Skip()
        
    def OnText(self, e):                
        filename = self.filename
        if self.IsModified():
            filename = filename + " *"            
        self.SetTitle(filename + " - " + appName)
        
        e.Skip()
        
        
###########################################
# edit
########################################### 

    # bug : insert 연속, del 연속 을 반복후 undo 하면 다른 글자 나옴 ( 아마 '\n' 의 n 인가? )
    # bug 그외... 덮어씀. 여튼 문제 많음 -.-'
    
    def OnUndo(self, e):
        self.historyMgr.Undo()
    
    def OnRedo(self, e):
        self.historyMgr.Redo()
        
    def OnShowFind(self, e):
        if self.control.IsEmpty():
            return
        dlg = wx.FindReplaceDialog(self, self.findReplaceData, "찾기"
                                   ,wx.FR_NOWHOLEWORD)
        dlg.Show(True)
    
    # 아래 변환코드는 임시이며, 윈도우에서 제작된 text 파일에만 먹힐 것이며, 뉴라인 처리만 고려한 것임.
    #    
    # 참조:http://xoomer.virgilio.it/infinity77/wxPython/Widgets/wx.TextCtrl.html
    # TextCtrl에서 사용하는 문자열 index와 GetValue로 받아온 index는 다름.
    # OS 호환 때문에 그런 경우가 생긴다고 함.
    # 뉴라인 문자만 생각했을 때, Windows는 \r\n을 사용하나 Unix에선 \n 만 사용.
    # 하지만 GetValue로 가져올땐 항상 \n 으로 처리하기에 index에 차이가 발생 가능.
    # API 문서상에서는 TextCtrl index와 GetValue에서의 index를 절대 혼용하지 마라고 함.
    # TextCtrl이 아닌 StyledTextCtrl 같은 다른 Control을 쓰는 건 어떨지.
         
    def GetWXIndex(self, str, strIndex):
        newlineCnt = str.count("\n", 0, strIndex)
        return strIndex + newlineCnt
    
    def OnFind(self, e):
        if e.GetEventType() == wx.wxEVT_COMMAND_FIND:
            self.findReplaceData.SetFindString(e.GetFindString())
            self.findReplaceData.SetFlags(e.GetFlags())
            word = e.GetFindString()
        elif e.GetEventType() == wx.wxEVT_COMMAND_FIND_NEXT:
            word = self.findReplaceData.GetFindString()
        else:
            return
        
        value = self.control.GetValue()

        if not(self.findReplaceData.GetFlags() & wx.FR_MATCHCASE):
            word = word.upper()
            value = value.upper()
            
#            it doesn't work
#            have to use 're'
#        if not(self.findReplaceData.GetFlags() & wx.FR_WHOLEWORD):

        (selectionFrom, selectionTo) = self.control.GetSelection()
        if self.findReplaceData.GetFlags() & wx.FR_DOWN:
            wxString = self.control.GetRange(0,selectionTo)
            startPos = len(wxString)
            index = value.find(word, startPos)
        else:
            wxString = self.control.GetRange(0,selectionFrom)
            startPos = len(wxString)
            index = value.rfind(word, 0, startPos)
        
        if index != -1:
            wxIndex = self.GetWXIndex(value, index)
            self.control.SetSelection(wxIndex, wxIndex + len(word))
            self.control.ShowPosition(wxIndex)
        else:
            dlg = wx.MessageDialog(self, '"' + e.GetFindString() + '"을(를) 찾을 수 없습니다.'
                                   , appName, wx.OK | wx.ICON_INFORMATION)
            dlg.ShowModal()
            dlg.Destroy()

    def OnFindClose(self, e):
        e.GetDialog().Destroy()

    def OnShowReplace(self, evt):
        if self.control.IsEmpty():
            return
        dlg = wx.FindReplaceDialog(self, self.findReplaceData, "바꾸기"
                                   ,wx.FR_REPLACEDIALOG | wx.FR_NOWHOLEWORD)
        dlg.Show(True)
        
    # 버그 : 파일 맨마지막에 위치한 후 바꾸기를 하여 범위 벗어남 창이 두번 뜨고 
    #       그 창 두개를 닫는 순간 텍스트가 전체 선택된다
    def OnReplace(self, e):
        print 'OnReplace'
        self.findReplaceData.SetFindString(e.GetFindString())
        self.findReplaceData.SetFlags(e.GetFlags())
        findWord = e.GetFindString()
        replaceWord = e.GetReplaceString()
        value = self.control.GetValue()
        
        #################################
        # find
        # case-sensitive change real word
        findWordCase = findWord
        valueCase = value

        # case-sensitive
        if not(self.findReplaceData.GetFlags() & wx.FR_MATCHCASE):
            # case-sensitive change real word
            findWordCase = findWord.upper()
            valueCase = value.upper()
        
        # whole word    
#            it doesn't work
#            have to use 're'
#        if not(self.findReplaceData.GetFlags() & wx.FR_WHOLEWORD):

        
        (selectionFrom, selectionTo) = self.control.GetSelection()
        if self.findReplaceData.GetFlags() & wx.FR_DOWN:
            wxString = self.control.GetRange(0,selectionTo)
            startPos = len(wxString)
            index = valueCase.find(findWordCase, startPos)
        else:
            wxString = self.control.GetRange(0,selectionFrom)
            startPos = len(wxString)
            index = valueCase.rfind(findWordCase, 0, startPos)
        
        ##############################################
        # replace
        if index != -1:
            wxIndex = self.GetWXIndex(value, index)
                        
            self.control.Replace(wxIndex, wxIndex + len(findWord), replaceWord)
            self.historyMgr.AddReplaceCmd(wxIndex, findWord, replaceWord)
            self.control.SetSelection(wxIndex, wxIndex + len(replaceWord))
            self.control.ShowPosition(wxIndex)

        else:
            dlg = wx.MessageDialog(self, '"' + findWord + '"을(를) 찾을 수 없습니다.'
                                   , appName, wx.OK | wx.ICON_INFORMATION)
            dlg.ShowModal()
            dlg.Destroy()

    def OnReplaceAll(self, e):
        index = 0
        findWord = e.GetFindString()
        replaceWord = e.GetReplaceString()

        if not(self.findReplaceData.GetFlags() & wx.FR_MATCHCASE):
            findWord = findWord.upper()

        while index != -1:
            value = self.control.GetValue()

            if not(self.findReplaceData.GetFlags() & wx.FR_MATCHCASE):
                value = value.upper()
                
            (selectionFrom, selectionTo) = self.control.GetSelection()
            wxString = self.control.GetRange(0,selectionTo)
            startPos = len(wxString)
            index = value.find(findWord, startPos)
            
            if index != -1:
                wxIndex = self.GetWXIndex(value, index)
                
                self.control.Replace(wxIndex, wxIndex + len(findWord), replaceWord)
                self.control.SetSelection(wxIndex, wxIndex + len(findWord))
                self.control.ShowPosition(wxIndex)
    
    def OnGoto(self, e):
        if self.control.IsEmpty():
            return

#        if not self.panel:
#            self.panel = wx.Panel(
        
        line = wx.GetNumberFromUser("input line", "", "Goto", 1, 1, 10000000, self)
        if line >= 1:
            pos = self.control.XYToPosition(0, line - 1)
            if pos != -1:
                self.control.SetInsertionPoint(pos)
                self.control.ShowPosition(pos)
            else:
                dlg = wx.MessageDialog(self, '줄 번호가 범위를 벗어납니다.'
                                       , appName + " - 줄 이동"
                                       , wx.OK | wx.ICON_INFORMATION)
                dlg.ShowModal()
                dlg.Destroy()
        
    def OnSelectAll(self, e):
        self.control.SelectAll()
        
app = wx.App(True)
frame = NotepadFrame(None, appName)
app.MainLoop()
